package com.ease.component.util {
   import com.ease.util.ComponentUtil;
   import com.ease.util.WeakReference;
   import com.ease.component.events.AdvancedMouseEvent;

   import flash.display.DisplayObject;
   import flash.display.InteractiveObject;
   import flash.events.Event;
   import flash.events.EventDispatcher;
   import flash.events.MouseEvent;
   import flash.external.ExternalInterface;
   import flash.utils.setTimeout;

   import mx.core.Application;
   import mx.core.FlexGlobals;
   import mx.managers.IFocusManagerComponent;

   [DefaultProperty("alwaysPreventDefaultMenu")]
   [Exclude(name = "activate", kind = "event")]
   [Exclude(name = "deactivate", kind = "event")]
   public class AdvancedMouseSupport extends EventDispatcher {
      [Embed(source = "/assets/scripts/MouseEventDispatcher.js", mimeType = "application/octet-stream")]
      /**
       * The script which supports the advanced mouse event notifications.
       *
       * @private
       */
      private static var _script:Class;

      /**
       * Indicates whether the default Flash context menu should be disabled
       * event if no event handler responds to the right-click event by
       * preventing default behavior.
       *
       * @private
       */
      private var _alwaysPreventDefaultMenu:Boolean;
      /**
       * Indicates whether an instance of this class has already been
       * initialized.
       *
       * @private
       */
      private var _initialized:Boolean;
      /**
       * A reference to the last <code>InteractiveObject</code> to be the
       * target of a mouse-down event.
       *
       * @private
       */
      private var _lastMouseDownTarget:WeakReference = new WeakReference();

      /**
       * Constructs a new <code>AdvancedMouseSupport</code>.
       */
      public function AdvancedMouseSupport() {
         super();
         if (!_initialized) {
            if (ExternalInterface.available && ExternalInterface.objectID) {
               ExternalInterface.call(new _script().toString(), ExternalInterface.objectID);
               ExternalInterface.addCallback("middleMouseDown", handleMiddleMouseDown);
               ExternalInterface.addCallback("middleMouseUp", handleMiddleMouseUp);
               ExternalInterface.addCallback("rightMouseDown", handleRightMouseDown);
               ExternalInterface.addCallback("rightMouseUp", delayedHandleRightMouseUp);
            }
            _initialized = true;
         }
      }

      /**
       * Calls the <code>handleRightMouseUp</code> function after the current
       * queued event handlers finish executing. This is necessary because when
       * holding down the right mouse button the Flash Player will stop
       * tracking the mouse position, and the event handler to update the
       * position when the right mouse button is released is queued to run
       * after the mouse up event.
       *
       * @param ctrlKey Indicates whether the Control key was pressed when the
       *            mosue event occurred.
       * @param altKey Indicates whether the Alt key was pressed when the
       *            mouse event occurred.
       * @param shiftKey Indicates whether the Shift key was pressed when the
       *             mouse event occurred.
       */
      private function delayedHandleRightMouseUp(ctrlKey:Boolean, altKey:Boolean, shiftKey:Boolean):void {
         setTimeout(handleRightMouseUp, 0, ctrlKey, altKey, shiftKey);
      }

      /**
       * Focuses on the specified target control as specifically as is possible.
       * This will climb the hierarchy of <code>DisplayObject</code>s until a
       * focusable object is found and focus on it.
       *
       * @param target The <code>DisplayObject</code> on which to focus.
       *
       * @private
       */
      private static function focus(target:DisplayObject):void {
         for (var focusTarget:DisplayObject = target; focusTarget != null; focusTarget = focusTarget.parent) {
            var focusableTarget:IFocusManagerComponent = focusTarget as IFocusManagerComponent;
            if (focusableTarget != null) {
               focusableTarget.setFocus();
               break;
            }
         }
      }

      /**
       * Handles notification that the middle mouse button was clicked. This
       * should react by dispatching a <code>MouseEvent</code> of type
       * <code>AdvancedMouseEvent.MIDDLE_CLICK</code> on the control to
       * which the mouse is pointing.
       *
       * @param ctrlKey Indicates whether the Control key was pressed when the
       *            mouse event occurred.
       * @param altKey Indicates whether the Alt key was pressed when the
       *            mouse event occurred.
       * @param shiftKey Indicates whether the Shift key was pressed when the
       *             mosue event occurred.
       */
      protected function handleMiddleClick(ctrlKey:Boolean, altKey:Boolean, shiftKey:Boolean):void {
         var target:InteractiveObject = mouseTarget;
         if (target) {
            var event:MouseEvent = new AdvancedMouseEvent(AdvancedMouseEvent.MIDDLE_CLICK, true, false,
                                               target.mouseX, target.mouseY, ctrlKey, altKey, shiftKey);
            target.dispatchEvent(event);
         }
      }

      /**
       * Handles notification that the middle mouse button was pressed. This
       * should react by dispatching a <code>MouseEvent</code> of type
       * <code>AdvancedMouseEvent.MIDDLE_MOUSE_DOWN</code> on the control to
       * which the mouse is pointing. It should also dispatch a
       * <code>MouseEvent.MOUSE_DOWN</code> event on the sandbox root to
       * ensure menus are hidden as they would be for a left click.
       *
       * @param ctrlKey Indicates whether the Control key was pressed when the
       *            mouse event occurred.
       * @param altKey Indicates whether the Alt key was pressed when the
       *            mouse event occurred.
       * @param shiftKey Indicates whether the Shift key was pressed when the
       *             mosue event occurred.
       */
      protected function handleMiddleMouseDown(ctrlKey:Boolean, altKey:Boolean, shiftKey:Boolean):void {
         var target:InteractiveObject = mouseTarget;
         if (target) {
            var mouseDownEvent:MouseEvent = new MouseEvent(MouseEvent.MOUSE_DOWN);
            FlexGlobals.topLevelApplication.systemManager.topLevelSystemManager.
               getSandboxRoot().dispatchEvent(mouseDownEvent);
            lastMouseDownTarget = target;
            var event:MouseEvent = new AdvancedMouseEvent(AdvancedMouseEvent.MIDDLE_MOUSE_DOWN, true, true,
                                               target.mouseX, target.mouseY, ctrlKey, altKey, shiftKey);
            target.dispatchEvent(event);
         }
      }

      /**
       * Handles notification that the middle mouse button was released. This
       * should react by dispatching a <code>MouseEvent</code> of type
       * <code>AdvancedMouseEvent.MIDDLE_MOUSE_UP</code> on the control to
       * which the mouse is pointing.
       *
       * <p>
       * This function also determines whether to call the handler for a
       * middle click. The mouse up event is also a click if it is on the same
       * target as the mouse down event. The click event should have the same
       * keyboard modifiers as the mouse up event.
       * </p>
       *
       * @param ctrlKey Indicates whether the Control key was pressed when the
       *            mouse event occurred.
       * @param altKey Indicates whether the Alt key was pressed when the
       *            mouse event occurred.
       * @param shiftKey Indicates whether the Shift key was pressed when the
       *             mosue event occurred.
       */
      protected function handleMiddleMouseUp(ctrlKey:Boolean, altKey:Boolean, shiftKey:Boolean):void {
         var target:InteractiveObject = mouseTarget;
         if (target) {
            var event:MouseEvent = new AdvancedMouseEvent(AdvancedMouseEvent.MIDDLE_MOUSE_UP, true, false,
                                               target.mouseX, target.mouseY, ctrlKey, altKey, shiftKey);
            target.dispatchEvent(event);
            if (target == lastMouseDownTarget) {
               handleMiddleClick(ctrlKey, altKey, shiftKey);
            }
         }
      }

      /**
       * Handles notification that the right mouse button was clicked. This
       * should react by dispatching a <code>MouseEvent</code> of type
       * <code>AdvancedMouseEvent.RIGHT_CLICK</code> on the control to
       * which the mouse is pointing.
       *
       * @param ctrlKey Indicates whether the Control key was pressed when the
       *            mouse event occurred.
       * @param altKey Indicates whether the Alt key was pressed when the
       *            mouse event occurred.
       * @param shiftKey Indicates whether the Shift key was pressed when the
       *             mosue event occurred.
       */
      protected function handleRightClick(ctrlKey:Boolean, altKey:Boolean, shiftKey:Boolean):void {
         var target:InteractiveObject = mouseTarget;
         if (target) {
            var event:MouseEvent = new AdvancedMouseEvent(AdvancedMouseEvent.RIGHT_CLICK, true, false,
                                               target.mouseX, target.mouseY, ctrlKey, altKey, shiftKey);
            target.dispatchEvent(event);
         }
      }

      /**
       * Handles notification that the right mouse button was pressed. This
       * should react by dispatching a <code>MouseEvent</code> of type
       * <code>AdvancedMouseEvent.RIGHT_MOUSE_DOWN</code> on the control to
       * which the mouse is pointing. It should also dispatch a
       * <code>MouseEvent.MOUSE_DOWN</code> event on the sandbox root to
       * ensure menus are hidden as they would be for a left click.
       *
       * @param ctrlKey Indicates whether the Control key was pressed when the
       *            mouse event occurred.
       * @param altKey Indicates whether the Alt key was pressed when the
       *            mouse event occurred.
       * @param shiftKey Indicates whether the Shift key was pressed when the
       *             mosue event occurred.
       *
       * @return A value indicating whether the default context menu should be
       *       suppressed.
       */
      protected function handleRightMouseDown(ctrlKey:Boolean, altKey:Boolean, shiftKey:Boolean):Boolean {
         var target:InteractiveObject = mouseTarget;
         if (target) {
            focus(target);
            var mouseDownEvent:MouseEvent = new MouseEvent(MouseEvent.MOUSE_DOWN);
            FlexGlobals.topLevelApplication.systemManager.topLevelSystemManager.
               getSandboxRoot().dispatchEvent(mouseDownEvent);
            lastMouseDownTarget = target;
            var event:MouseEvent = new AdvancedMouseEvent(AdvancedMouseEvent.RIGHT_MOUSE_DOWN, true, true,
                                               target.mouseX, target.mouseY, ctrlKey, altKey, shiftKey);
            target.dispatchEvent(event);
            return event.isDefaultPrevented() || alwaysPreventDefaultMenu;
         } else {
            return alwaysPreventDefaultMenu;
         }
      }

      /**
       * Handles notification that the right mouse button was released. This
       * should react by dispatching a <code>MouseEvent</code> of type
       * <code>AdvancedMouseEvent.RIGHT_MOUSE_UP</code> on the control to
       * which the mouse is pointing.
       *
       * <p>
       * This function also determines whether to call the handler for a
       * right click. The mouse up event is also a click if it is on the same
       * target as the mouse down event. The click event should have the same
       * keyboard modifiers as the mouse up event.
       * </p>
       *
       * @param ctrlKey Indicates whether the Control key was pressed when the
       *            mouse event occurred.
       * @param altKey Indicates whether the Alt key was pressed when the
       *            mouse event occurred.
       * @param shiftKey Indicates whether the Shift key was pressed when the
       *             mosue event occurred.
       */
      protected function handleRightMouseUp(ctrlKey:Boolean, altKey:Boolean, shiftKey:Boolean):void {
         var target:InteractiveObject = mouseTarget;
         if (target) {
            var event:MouseEvent = new AdvancedMouseEvent(AdvancedMouseEvent.RIGHT_MOUSE_UP, true, false,
                                               target.mouseX, target.mouseY, ctrlKey, altKey, shiftKey);
            target.dispatchEvent(event);
            if (target == lastMouseDownTarget) {
               handleRightClick(ctrlKey, altKey, shiftKey);
            }
         }
      }

      [Bindable("alwaysPreventDefaultMenuChanged")]
      [Inspectable(category = "General", default = "false")]
      /**
       * Indicates whether the default Flash context menu should be disabled
       * event if no event handler responds to the right-click event by
       * preventing default behavior.
       *
       * <p>
       * If this value is set to <code>false</code>, the default Flash context
       * menu will only be disabled if a handler for the right-click event
       * calls <code>event.preventDefault()</code>.
       * </p>
       *
       * @default false
       */
      public function get alwaysPreventDefaultMenu():Boolean {
         return _alwaysPreventDefaultMenu;
      }

      /**
       * @private
       */
      public function set alwaysPreventDefaultMenu(value:Boolean):void {
         if (_alwaysPreventDefaultMenu != value) {
            _alwaysPreventDefaultMenu = value;
            dispatchEvent(new Event("alwaysPreventDefaultMenuChanged"));
         }
      }

      /**
       * The last <code>DisplayObject</code> to be the target of a
       * <code>AdvancedMouseEvent.RIGHT_MOUSE_DOWN</code> or
       * <code>AdvancedMouseEvent.MIDDLE_MOUSE_DOWN</code> event.
       *
       * @default undefined
       */
      protected function get lastMouseDownTarget():InteractiveObject {
         return _lastMouseDownTarget.object as InteractiveObject;
      }

      /**
       * @private
       */
      protected function set lastMouseDownTarget(value:InteractiveObject):void {
         _lastMouseDownTarget.object = value;
      }

      /**
       * The <code>InteractiveObject</code> currently targeted by the mouse.
       */
      protected function get mouseTarget():InteractiveObject {
         var target:DisplayObject =
            ComponentUtil.findObjectUnderMouse(DisplayObject(FlexGlobals.topLevelApplication).stage);
         for (; target != null; target = target.parent) {
            var interactiveObject:InteractiveObject = target as InteractiveObject;
            if (interactiveObject && interactiveObject.mouseEnabled) {
               return interactiveObject;
            }
         }
         return null;
      }
   }
}
